Hauptseite Zu yanwittmann.de

Zur Hauptseite

<     6. Unterprogrammtechnik     >

Software >> Java & OOP


Unterprogramme dienen dazu, Programmcode übersichtlicher und weniger repetitiv zu machen, indem Teile eines Programms in besagte Unterprogramme ausgelagert werden.
Hier sind einige Eigenschaften von Unterprogrammen:


Doch was sind Unterprogramme? Unterprogramme können Code enthalten und diesen Ausführen, wenn das Unterprogramm aufgerufen wird. Man stelle sich vor, man hat ein Programm, das eine gewisse Menge an immer gleichem Code an verschiedenen Stellen ausführen:

public static void main(String[] args) {     
    int x = 23;
    x = x + 4;
    if (x < 54)
        x = 34;
    System.out.println(x);
    x = 67;
    x = x + 4;
    if (x < 54)
        x = 34;
    System.out.println(x);
}
Java

Hier müsste man den selben Code zwei mal hinschreiben, was eher umständlich ist. Zudem müsste man wenn man das Stück Code ändern möchte, es zwei mal bearbeiten.
Die Lösung dazu sind Unterprogramme:

public static void main(String[] args) {     
    int x = 23;
    System.out.println(eineFunktion(x));     
    x = 67;
    System.out.println(eineFunktion(x));
}
 
public int eineFunktion(int einParameter) {
    einParameter = einParameter + 4;
    if (einParameter < 54)
        einParameter = 34;
    return einParameter;
}
Java

Und voilà, sehr viel eleganter. Der doppelt vorkommende Code wurde in ein anderes Unterprogramm verschoben. Doch was genau ist da jetzt passiert? Schauen wir uns das genauer an.


Erstellen eines Unterprogramms

Es gibt bei einem Unterprogramm einmal die Definition und einmal die Aufrufe des Unterprogramms. Sagen wir, wir haben ein Unterprogramm gibAus was ein eindimensionales Feld ausgeben soll:

public void gibAus(String[] meinFeld) {     
    for (int i = 0; i < meinFeld.length; i++) {     
        System.out.println(meinFeld[i]);
    }
}
Java

Dies ist die Definition des Unterprogramms. In ihm wird definiert, was passieren soll, wenn das Unterprogramm aufgerufen wird. Die erste Zeile des Unterprogramms, also public void gibAus(String[] meinFeld) in diesem Fall, nennt sich Signatur. Die Signatur des Unterprogramms gibt relativ viele Informationen:

                                                                                                        
NameErklärungMögliche Werte
SichtbarkeitDie Sichtbarkeit beschreibt, von wo aus das Unterprogramm aus aufgerufen werden kann.
public macht, dass jeder, der auf ein Objekt der Klasse zugriff hat, darauf zugreifen kann.
private bedeutet, dass nur das Objekt selbst es aufrufen kann.
protected heißt, dass nur das Objekt selbst und davon erbende Klassen es aufrufen können.
public
private
protected
RückgabetypJedes Unterprogramm kann einem einen Wert zurückgeben, nachdem es ausgeführt wurde. Der Rückgabetyp beschreibt den Typ des Wertes, der zurückgegeben werden soll.
Wenn ein Unterprogramm keinen Wert zurückgeben soll, muss als Rückgabetyp void verwendet werden. Wenn aber etwas anderes da steht, z.B. String, dann muss auch ein Wert von diesem Typ zurückgegeben werden. Dies passiert mit return.
void
String
int
...
NameSchlicht gesagt: Der Name des Unterprogramms. Für ihn gelten die gleichen Regeln wie für Variablen. Dieser wird später auch beim Unterprogrammaufruf verwendet.

meinUnterprogramm
gibAus
berechneWerte
...
ParameterlisteEine beliebig lange Liste an Werten, die dem Unterprogramm beim Aufruf mitgegeben werden müssen. Die einzelnen Parameter werden durch , getrennt und haben zuerst den Typ und dann den Bezeichner. Sie stehen in den Klammern des Unterprogramms.String text, int wert

Also, das Unterprogramm public void gibAus(String[] meinFeld) von oben hat die Sichtbarkeit public (kann also von überall aus aufgerufen werden), gibt keinen Wert zurück (void) und nimmt einen Parameter, ein String Array (String[]), das es dann meinFeld nennt. Hier die einzelnen Teile im Detail:


Parameter

Ein Parameter eines Unterprogramms ist das, was man beim Unterprogrammaufruf mitgeben kann. Hier ein Beispiel:

public static void main(String[] args) {     
    printCombined(1, "Thomas");

    int meineID = 2;
    String name = "Yan";
    printCombined(meineID, name);
}

public void printCombined(int id, String username) {     
    System.out.println("(" + id + ") " + username);
}
Java

Wenn man in dem Beispiel die main ausführen würde, würde (1) Thomas und (2) Yan ausgegeben werden. Warum?
Das Unterprogramm printCombined nimmt zwei Parameter an: einen integer (id) und einen Text (username). Der erste Unterprogrammaufruf printCombined(1, "Thomas"); nimmt direkt als Parameter an: 1, "Thomas". Diese Werte werden dann den Variablen id und username unten im Unterprogramm eingesetzt. Das heißt, id = 1 und username = "Thomas". Sobald das Unterprogramm fertig abgelaufen ist, kehrt das Programm zum Aufruf zurück.
Als nächstes haben wir zwei Variablen meineID und name. Ihnen sind die Werte 2 und "Yan" zugeordnet. Das heißt, hier werden dann die Werte der Variablen als Parameter mitgegeben und es wird im Unterprogramm (2) Yan ausgegeben.

Parameter sind also nichts anderes als Werte, die beim Aufruf an das Unterprogramm übergeben werden können.
Bei der Parameterübergabe gibt es allerdings zwei unterschiedliche Fälle: Call by value und call by reference. Diese beiden wurden bereits bei den Feldern angedeutet.

Call by value
Bei call by value wird ein Wert als Parameter an das Unterprogramm übergeben. Dies trifft auf alle primitiven Datentypen zu, also alle, die klein geschrieben werden (int, char). Hierbei wird also der Wert kopiert und diese Kopie des Wertes an das Unterprogramm übergeben.

Wenn also in diesem Beispiel meinWert mit dem Wert von 10 an doStuff übergeben wird, so wird eine Kopie des Wertes 10 erstellt und in die Variable meinParameter gepackt. Wenn diese Kopie (meinParameter) verändert wird, bleibt das original (meinWert) unverändert.

public static void main(String[] args) {     
    int meinWert = 10;
    doStuff(meinWert);
}

public void doStuff(int meinParameter) {     
    System.out.println(meinParameter);
}
Java



Wie im Bild zu sehen ist, wurde der Wert an eine andere Adresse im Speicher kopiert und beide Variablen haben somit eine eigene Kopie des Wertes.

Call by reference
Bei call by Reference wird nicht der Wert der angegebenen Variable mitgegeben, sondern die Referenz, also ein Zeiger, zum eigentlichen Wert mitgegeben. Hier sind das alle restlichen Typen, also Objekte und Felder.
Bei dem Übergebenen Wert handelt es sich also tatsächlich um den exakt gleichen Wert, nicht bloß eine Kopie davon. Wenn man den Wert der Variable dann im Unterprogramm verändert, so verändert sich auch der originale Wert der originalen Variable.

public static void main(String[] args) {     
    int[] meinFeld = {10, 24, 8, 93, 861, 200};     
    doStuff(meinFeld);
    System.out.println(meinFeld[0]);
}

public void doStuff(int[] meinParameter) {     
    meinParameter[0] = 453;
    System.out.println(meinParameter[0]);
}
Java

In diesem Beispiel wird ein Feld int[] meinFeld mitgegeben (also kein primitiver Datentyp). Es wird hierbei also keine Kopie angefertigt, es ist noch immer das gleiche Feld wie oben. Wenn es also dort verändert wird: meinParameter[0] = 453, dann wird es auch oben verändert und der Wert bei 0 ist nicht mehr 10, sondern 453. Es würde also zwei mal 453 ausgegeben werden.


Es handelt sich um ein und dasselbe Objekt.


Rückgabewerte

Ein Unterprogramm kann einem auch Werte zurückgeben. Dieser wird, wie oben beschrieben, ebenfalls in der Signatur des Unterprogramms angegeben. Wenn man nicht möchte, dass das Unterprogramm einem einen Wert zurückgibt, muss man als Rückgabetyp void angeben.

Zurückgegeben wird in einem Unterprogramm mit return ...;, wobei der zurückzugebende Wert hinter dem return angegeben wird. Sobald das Unterprogramm auf ein return stößt, verlässt es sofort das Unterprogramm, es ist also immer das letzte, was man in einem Unterprogramm tut. Der angegebene Wert muss von dem Typ sein, der oben in der Signatur des Unterprogramms angegeben ist, ansonsten gibt es einen Kompilierfehler.

Es kann auch mehrere return-Anweisungen im Programm geben:

public static void main(String[] args) {     
    System.out.println(berechne(12, 2));     
    System.out.println(berechne(3, 8));
    System.out.println(berechne(20, 34));     
}

public int berechne(int a, int b) {
    a = a * 2;
    if (a < 10)
        return a * (b + 2);
    else if (b < 10)
        return a * (b - 2);
    return a + b;
}
Java

In diesem Beispiel würde 0, 60 und 74 ausgegeben werden. Beim berechne(12, 2) verlässt es das Unterprogramm beim zweiten return, bei berechne(3, 8) beim ersten und bei berechne(20, 34) beim letzten. Wie zu erwarten springt das Programm sobald es auf das return stößt zurück zum Unterprogrammaufruf und setzt anstelle des Aufrufs den zurückgegebenen Wert ein, also steht in main eigentlich das hier:

public static void main(String[] args) {     
    System.out.println(0);
    System.out.println(60);
    System.out.println(74);
}
Java



Basierend auf dem Rückgabewert-Typ wird auch die Namensgebung bestimmt:


Formalparameter und Aktualparameter

Je nachdem, ob der Parameter in der Unterprogrammdefinition oder im Aufruf steht, wird er unterschiedlich benannt.

Die Parameter in der Definition eines Unterprogramms (also in der Signatur) werden Formalparameter genannt. In diesem Beispiel hier wäre also String textParameter ein Formalparameter, da es sich bei dem Code um eine Unterprogrammdefinition handelt:

private String doStuff(String textParameter) {     
    //...
}
Java


Wenn nun das Unterprogramm aufgerufen wird, so werden für die benötigten Parameter konkrete Werte eingesetzt. Das sind dann die Aktualparameter. Die beiden hier konkreten mitgegebenen Werte sind also Aktualparameter:

doStuff("Dies ist ein Aktualparameter");     
doStuff("Mein Text".substring(1,4));
Java


Aufgaben

    Frage 1
Wie werden bei einem Unterprogramm Parameter angegeben?

    Frage 2
Was für mögliche Rückgabewerte gibt es für ein Unterprogramm? Wie nennt man dann je das Unterprogramm?

    Frage 3
Was ist der Unterschied zwischen call by value und call by reference?

    Frage 4
Im Beispiel oben, was würde berechne(56, 3), berechne(2, 6) und berechne(5, 10) zurückgeben?

    Frage 5
Erstelle ein Unterprogramm, das einen Text als Parameter erhält und den doppelten Ganzzahlwert zurückgibt. Erstelle drei Aufrufe und deren Rückgaben dafür.

    Frage 6
Extrahiere sinnvoll Unterprogramme aus dem Programm unten:

public static void main(String[] args) {     
    int[] meineWerte = {1, 45, 23, 76, 34};     
    for (int i = 0; i < meineWerte.length; i++) {     
        System.out.println(meineWerte[i]);
    }
    for (int i = 0; i < meineWerte.length; i++) {
        if (meineWerte[i] < 30) {
            meineWerte[i] = meineWerte[i] / 2;
        } else {
            meineWerte[i] = meineWerte[i] + 4;
        }
    }
    for (int i = 0; i < meineWerte.length; i++) {
        System.out.println(meineWerte[i]);
    }
    for (int i = 0; i < meineWerte.length; i++) {
        if (meineWerte[i] < 30) {
            meineWerte[i] = meineWerte[i] / 2;
        } else {
            meineWerte[i] = meineWerte[i] + 4;
        }
    }
    for (int i = 0; i < meineWerte.length; i++) {
        System.out.println(meineWerte[i]);
    }
}
Java

    Frage 7
Liste alle Aktual-/Formalparameter im Code hier drunter auf:

public static void main(String[] args) {     
    gibAus(berechne(12, 34));
    int x = 4;
    gibAus(berechne(x, x + 45) + 6);
}

private int berechne(int a, int b) {
    return a - b * 2;
}

private void gibAus(int ergebnis) {
    System.out.println(ergebnis);
}
Java

    Frage 8
Denk dir selbst ein Unterprogramm aus, das irgendetwas tut, was sich lohnt auszulagern und du z.B. immer mal wieder gebrauchen könntest!